package com.easylinkin.linkappapi.mechanical.util;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class PointToPolygonDistanceWithFoot {

    public static class DistanceResult {
        public final double distance;
        public final Point2D footPoint;
        public final Point2D lineStart;
        public final Point2D lineEnd;

        public DistanceResult(double distance, Point2D footPoint, Point2D lineStart, Point2D lineEnd) {
            this.distance = distance;
            this.footPoint = footPoint;
            this.lineStart = lineStart;
            this.lineEnd = lineEnd;
        }
    }

    /**
     * 计算点到多边形的最短距离及垂点
     * @param point 待测点
     * @param polygon 多边形顶点列表（按顺序）
     * @return 包含最短距离、垂点及对应边的结果对象
     */
    public static DistanceResult calculateMinDistanceWithFoot(Point2D.Double point, List<Point2D.Double> polygon) {
        if (polygon == null || polygon.size() < 2) {
            throw new IllegalArgumentException("多边形至少需要2个顶点");
        }

        DistanceResult minResult = null;
        double minDistance = Double.MAX_VALUE;

        // 检查多边形是否闭合，如果没有则闭合它
        if (!polygon.get(0).equals(polygon.get(polygon.size() - 1))) {
            polygon.add(new Point2D.Double(polygon.get(0).getX(), polygon.get(0).getY()));
        }

        // 遍历多边形的每条边
        for (int i = 0; i < polygon.size() - 1; i++) {
            Point2D.Double p1 = polygon.get(i);
            Point2D.Double p2 = polygon.get(i + 1);

            // 计算点到当前线段的距离和垂点
            DistanceResult result = pointToLineSegmentDistanceWithFoot(point, p1, p2);

            // 更新最小距离和结果
            if (result.distance < minDistance) {
                minDistance = result.distance;
                minResult = result;
            }
        }

        return minResult;
    }

    /**
     * 计算点到线段的距离及垂点
     * @param point 待测点
     * @param lineStart 线段起点
     * @param lineEnd 线段终点
     * @return 包含距离、垂点及线段的结果对象
     */
    private static DistanceResult pointToLineSegmentDistanceWithFoot(Point2D.Double point, Point2D.Double lineStart, Point2D.Double lineEnd) {
        // 线段长度的平方
        double lengthSquared = lineStart.distanceSq(lineEnd);

        // 如果线段长度为0，退化为点到点的距离
        if (lengthSquared == 0.0) {
            return new DistanceResult(point.distance(lineStart), lineStart, lineStart, lineEnd);
        }

        // 计算投影比例 t = [(point-p1) . (p2-p1)] / |p2-p1|^2
        double t = ((point.getX() - lineStart.getX()) * (lineEnd.getX() - lineStart.getX()) +
                (point.getY() - lineStart.getY()) * (lineEnd.getY() - lineStart.getY())) / lengthSquared;

        // 限制 t 在 [0,1] 范围内，确保投影点在线段上
        t = Math.max(0, Math.min(1, t));

        // 计算垂点坐标
        double projectionX = lineStart.getX() + t * (lineEnd.getX() - lineStart.getX());
        double projectionY = lineStart.getY() + t * (lineEnd.getY() - lineStart.getY());
        Point2D.Double footPoint = new Point2D.Double(projectionX, projectionY);

        Double distance = CraneUtil.getDistance(point.getX(), point.getY(), footPoint.getX(), footPoint.getY());
        // 返回结果对象
        return new DistanceResult(distance, footPoint, lineStart, lineEnd);
    }

    /**
     * 计算点到多边形的最短距离及垂点（平面坐标系版本）
     * @param point 待测点（平面坐标，单位：米）
     * @param polygon 多边形顶点列表（平面坐标，单位：米）
     * @return 包含最短距离、垂点及对应边的结果对象
     */
    public static DistanceResult calculateMinDistanceWithFootPlanar(Point2D.Double point, List<Point2D.Double> polygon) {
        if (polygon == null || polygon.size() < 2) {
            throw new IllegalArgumentException("多边形至少需要2个顶点");
        }

        DistanceResult minResult = null;
        double minDistance = Double.MAX_VALUE;

        // 检查多边形是否闭合，如果没有则闭合它
        if (!polygon.get(0).equals(polygon.get(polygon.size() - 1))) {
            polygon.add(new Point2D.Double(polygon.get(0).getX(), polygon.get(0).getY()));
        }

        // 遍历多边形的每条边
        for (int i = 0; i < polygon.size() - 1; i++) {
            Point2D.Double p1 = polygon.get(i);
            Point2D.Double p2 = polygon.get(i + 1);

            // 计算点到当前线段的距离和垂点（平面坐标系版本）
            DistanceResult result = pointToLineSegmentDistanceWithFootPlanar(point, p1, p2);

            // 更新最小距离和结果
            if (result.distance < minDistance) {
                minDistance = result.distance;
                minResult = result;
            }
        }

        return minResult;
    }

    /**
     * 计算点到线段的距离及垂点（平面坐标系版本）
     * @param point 待测点（平面坐标，单位：米）
     * @param lineStart 线段起点（平面坐标，单位：米）
     * @param lineEnd 线段终点（平面坐标，单位：米）
     * @return 包含距离、垂点及线段的结果对象
     */
    private static DistanceResult pointToLineSegmentDistanceWithFootPlanar(Point2D.Double point, Point2D.Double lineStart, Point2D.Double lineEnd) {
        // 线段长度的平方
        double lengthSquared = lineStart.distanceSq(lineEnd);

        // 如果线段长度为0，退化为点到点的距离
        if (lengthSquared == 0.0) {
            return new DistanceResult(point.distance(lineStart), lineStart, lineStart, lineEnd);
        }

        // 计算投影比例 t = [(point-p1) . (p2-p1)] / |p2-p1|^2
        double t = ((point.getX() - lineStart.getX()) * (lineEnd.getX() - lineStart.getX()) +
                (point.getY() - lineStart.getY()) * (lineEnd.getY() - lineStart.getY())) / lengthSquared;

        // 限制 t 在 [0,1] 范围内，确保投影点在线段上
        t = Math.max(0, Math.min(1, t));

        // 计算垂点坐标
        double projectionX = lineStart.getX() + t * (lineEnd.getX() - lineStart.getX());
        double projectionY = lineStart.getY() + t * (lineEnd.getY() - lineStart.getY());
        Point2D.Double footPoint = new Point2D.Double(projectionX, projectionY);

        // 使用欧几里得距离计算（平面坐标系）
        double distance = point.distance(footPoint);

        // 返回结果对象
        return new DistanceResult(distance, footPoint, lineStart, lineEnd);
    }

    public static void main(String[] args) {
        // 示例用法
        Point2D.Double point = new Point2D.Double(114.15997806796778, 30.613522643949864);
        List<Point2D.Double> polygon = new ArrayList<>();
        polygon.add(new Point2D.Double(114.15959949531714, 30.614003545741696));
        polygon.add(new Point2D.Double(114.1598122951374, 30.613712322662412));
        polygon.add(new Point2D.Double(114.16135576727723, 30.614595309545454));
        polygon.add(new Point2D.Double(114.16105407679622, 30.614858576950578));

        DistanceResult result = calculateMinDistanceWithFoot(point, polygon);

        System.out.println("点到多边形的最短距离: " + result.distance);
        System.out.println("垂点坐标: (" + result.footPoint.getX() + ", " + result.footPoint.getY() + ")");
        System.out.println("对应的边: 从 (" + result.lineStart.getX() + ", " + result.lineStart.getY() +
                ") 到 (" + result.lineEnd.getX() + ", " + result.lineEnd.getY() + ")");
    }
}